// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.

const fs = require('fs');
const {drawCpuLineChart, drawCpuHistogram, drawMemLineChart, drawMemHistogram} = require('./draw.js')

const procDir = "/proc/";

const UPDATE_INTERVAL = 500;
const RECORD_SIZE = 30;
const cpuUsageArr = new Array(RECORD_SIZE);
const memUsageArr = new Array(RECORD_SIZE);

let oldProcessDatas = null;

updateTimeInfo();
initKernelInfo();
initCpuBasicInfo();
initMemBasicInfo();
initCpuUsageInfo();
initMemUsageInfo();
initProcessInfo();


function parseData(data) {
  let items = data.split('\n');
  let result = [];
  for (let item of items) {
    let [key, value] = item.split(':');
    if (key && value) {
      result[key.trim()] = value.trim();
    }
  }
  return result;
}

// time info
String.prototype.toHHMMSS = function () {
  let sec_num = parseInt(this, 10); // don't forget the second param
  let hours = Math.floor(sec_num / 3600);
  let minutes = Math.floor((sec_num - (hours * 3600)) / 60);
  let seconds = sec_num - (hours * 3600) - (minutes * 60);

  if (hours < 10) {
    hours = "0" + hours;
  }
  if (minutes < 10) {
    minutes = "0" + minutes;
  }
  if (seconds < 10) {
    seconds = "0" + seconds;
  }
  return hours + ' : ' + minutes + ' : ' + seconds;
};

function updateTimeInfo() {
  fs.readFile(procDir + 'uptime', 'utf-8', (err, data) => {
    if (err) throw data;
    $('#runtime').text(data.split(' ')[0].toHHMMSS());
    setTimeout(updateTimeInfo, UPDATE_INTERVAL);
  })
  ;
}

// kernel info
function initKernelInfo() {
  fs.readFile(procDir + 'version', 'utf-8', (err, data) => {
    if (err) throw err;
    let result = {};
    let regexArr;
    let versionRegex = /Linux version ([\w\.-]+)/;
    let compilerRegex = /gcc version ([\d\.]+)/;
    if (regexArr = versionRegex.exec(data)) {
      result.version = regexArr[0];
    }
    if (regexArr = compilerRegex.exec(data)) {
      result.compiler = regexArr[0];
    }
    result.time = data.slice(-29);
    setKernelInfo(result);
  })
  ;
}

function setKernelInfo(data) {
  $('#kernel-version').text(data['version']);
  $('#kernel-compiler').text(data['compiler']);
  $('#kernel-time').text(data['time']);
}

// cpu basic info

function initCpuBasicInfo() {
  fs.readFile(procDir + 'cpuinfo', 'utf-8', (err, data) => {
    if (err) throw err;
    setCpuBasicInfo(data);
  })
  ;
}

function setCpuBasicInfo(data) {
  data = parseData(data);
  $('#cpu-num').text(parseInt(data['processor']) + 1);
  $('#cpu-type').text(data['model name']);
  $('#cpu-hz').text(data['cpu MHz'] + ' MHz');
  $('#cpu-cache').text(data['cache size']);
}

// memory basic info
function initMemBasicInfo() {
  fs.readFile(procDir + 'meminfo', 'utf-8', (err, data) => {
    if (err) throw err;
    setMemBasicInfo(data);
  })
  ;
}

function setMemBasicInfo(data) {
  data = parseData(data);
  $('#mem-total').text(data['MemTotal']);
  $('#mem-free').text(data['MemFree']);
  $('#mem-ava').text(data['MemAvailable']);
  $('#mem-buf').text(data['Buffers']);
  $('#mem-cached').text(data['Cached']);
}

// cpu usage info
function initCpuUsageInfo() {
  fs.readFile(procDir + 'stat', 'utf-8', (err, data) => {
    if (err) throw err;
    let regex = /cpu[ \d]\s+(\d+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)/g;
    let result = [];
    let matchArr;
    while (matchArr = regex.exec(data)) {
      result.push({
        'userTime': matchArr[1],
        'nicedTime': matchArr[2],
        'systemTime': matchArr[3],
        'idleTime': matchArr[4],
        'iowaitTime': matchArr[5],
        'irqTime': matchArr[6],
        'softirqTime': matchArr[7],
        'totalTime': matchArr.slice(1, 8).reduce((a, b) => parseInt(a) + parseInt(b), 0).toString()
      })
      ;
    }
    cpuUsageArr.push(result);
    cpuUsageArr.shift();
    updateCpuInfo();
    updateCpuSvg();
  });
}

function calCpuUsage(lastData, currentData) {
  let totalTime = parseInt(currentData.totalTime) - parseInt(lastData.totalTime);
  let idleTime = parseInt(currentData.idleTime) - parseInt(lastData.idleTime);
  return 1 - idleTime / totalTime;
}

function updateCpuInfo() {
  // total usage update
  let [lastDatas, currentDatas] = cpuUsageArr.slice(-2);
  if (lastDatas) {
    for (let i = 0; i < currentDatas.length; i++) {
      currentDatas[i].usage = calCpuUsage(lastDatas[i], currentDatas[i]);
    }
    $('#cpu-usage').text((100 * currentDatas[0].usage).toFixed(2) + '%');
  }
  setTimeout(initCpuUsageInfo, UPDATE_INTERVAL);
}

// memory usage info
function initMemUsageInfo() {
  fs.readFile(procDir + 'meminfo', 'utf-8', (err, data) => {
    if (err) throw data;
    data = parseData(data);
    memUsageArr.push(data);
    memUsageArr.shift();
    updateMemInfo();
  })
  ;
}

function calMemUsage(data) {
  return 1 - parseInt(data['MemAvailable']) / parseInt(data['MemTotal']);
}

function updateMemInfo() {
  let currentData = memUsageArr[memUsageArr.length - 1];
  currentData.usage = calMemUsage(currentData);
  $('#memory-usage').text((100 * currentData.usage).toFixed(2) + '%');
  updateMemorySvg();
  setTimeout(initMemUsageInfo, UPDATE_INTERVAL);
}

// process info
fs.readFileAsync = function (filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
};

function initProcessInfo() {
  fs.readdir(procDir, 'utf-8', (err, data) => {
    if (err) throw err;
    let results = [];
    let ids = data.filter((file) => /\d+/.test(file));
    let promises = [];
    // load all /proc/<pid>/stat files
    for (let i = 0; i < ids.length; i++) {
      promises.push(fs.readFileAsync(procDir + ids[i] + '/stat'));
    }
    Promise.all(promises).then((datas) => {
      for (let data of datas) {
        let items = data.split(' ');
        results.push({
          'id': items[0],
          'name': items[1].slice(1, -1),
          'cpu': null,
          'cpuTotal': items.slice(13, 17).reduce((a, b) => parseInt(a) + parseInt(b), 0), // utime + stime + cutime + cstime
          'memoryTotal': null
        });
      }
    }).then(() => {
      // load all /proc/<pid>/statm files
      let new_promises = [];
      for (let i = 0; i < ids.length; i++) {
        new_promises.push(fs.readFileAsync(procDir + ids[i] + '/statm'));
      }
      Promise.all(new_promises).then((datas) => {
        for (let [index, data] of datas.entries()) {
          let items = data.split(' ');
          results[index].memoryTotal = ((parseInt(items[1]) - parseInt(items[2])) * 4 / 1024).toFixed(2); // resident memory size - share memory size
        }
        updateProcessInfo(results);
      }).catch((err) => {
        throw err;
      });
    }).catch((err) => {
      throw err;
    });
  });
}

function updateProcessInfo(datas) {
  $('#process-number').text(datas.length);
  if (!oldProcessDatas || !cpuUsageArr[RECORD_SIZE - 2]) {
    // first time
    oldProcessDatas = datas;
    setTimeout(initProcessInfo, UPDATE_INTERVAL);
    return;
  }
  let cpuTotal = parseInt(cpuUsageArr[RECORD_SIZE - 1][0].totalTime - cpuUsageArr[RECORD_SIZE - 2][0].totalTime);
  let $tableBody = $('#process-table-body');
  $tableBody.empty();
  for (let [index, data] of datas.entries()) {
    if (oldProcessDatas[index]) {
      data.cpu = ((data.cpuTotal - oldProcessDatas[index].cpuTotal) / cpuTotal * 100).toFixed(2);
    }
    else {
      data.cpu = 'N/A';
    }
    $tableBody.append(`
    <tr>
      <td>${data.name}</td>
      <td>${data.id}</td>
      <td>${data.cpu}</td>
      <td>${data.memoryTotal} Mib</td>
    </tr>
      `);
  }
  oldProcessDatas = datas;
  setTimeout(initProcessInfo, UPDATE_INTERVAL);
}


function updateCpuSvg() {
  // for line chart
  if (!cpuUsageArr[RECORD_SIZE - 2]) {
    drawCpuLineChart(null, 'cpuLineChart', true);
  }
  else {
    let cpusData = [];
    for (let i = 0; i < cpuUsageArr[RECORD_SIZE - 1].length; i++) {
      let historyData = [];
      for (let j = 0; j < RECORD_SIZE; j++) {
        let history = cpuUsageArr[j] ? cpuUsageArr[j][i] : {usage: 0};
        historyData[j] = {
          'index': j,
          'usage': Number(history.usage || 0) // history.usage may be undefined
        };
      }
      cpusData.push(historyData)
    }
    drawCpuLineChart(cpusData, 'cpuLineChart', false);
  }
  // for Histogram
  for (let i = 1; i < cpuUsageArr[RECORD_SIZE - 1].length; i++) {
    let currentCpuData = cpuUsageArr[RECORD_SIZE - 1][i];
    let lastCpuData = cpuUsageArr[RECORD_SIZE - 2] ? cpuUsageArr[RECORD_SIZE - 2][i] : undefined;
    if (!lastCpuData) {
      drawCpuHistogram(null, `cpu${i-1}-svg`, true);
      continue;
    }
    let total = parseInt(currentCpuData.totalTime) - parseInt(lastCpuData.totalTime);
    let data = [
      (parseInt(currentCpuData.userTime) - parseInt(lastCpuData.userTime)) / total,
      (parseInt(currentCpuData.nicedTime) - parseInt(lastCpuData.nicedTime)) / total,
      (parseInt(currentCpuData.systemTime) - parseInt(lastCpuData.systemTime)) / total,
      (parseInt(currentCpuData.idleTime) - parseInt(lastCpuData.idleTime)) / total,
      (parseInt(currentCpuData.irqTime) - parseInt(lastCpuData.irqTime)) / total,
      (parseInt(currentCpuData.softirqTime) - parseInt(lastCpuData.softirqTime)) / total
    ];
    drawCpuHistogram(data, `cpu${i-1}-svg`, false);
  }
}

function updateMemorySvg() {
  // for line chart
  if (!memUsageArr[RECORD_SIZE - 2]) {
    drawMemLineChart(null, 'memLineChart', true);
  }
  else {
    let datas = [];
    for (let i = 0; i < RECORD_SIZE - 1; i++) {
      let memData = memUsageArr[i] || {usage:0};
      datas.push({
        'index': i,
        'usage': Number(memData.usage)
      })
    }
    drawMemLineChart(datas, 'memLineChart', false);
  }
  // for Histogram
  let currentData = memUsageArr[RECORD_SIZE - 1];
  if (currentData) {
    const base = 1024;
    let data = [
      (currentData.usage * parseInt(currentData.MemTotal)) / base,
      parseInt(currentData.MemFree) / base,
      parseInt(currentData.MemAvailable) / base,
      parseInt(currentData.Buffers) / base,
      parseInt(currentData.Cached) / base,
      parseInt(currentData.MemTotal) / base
    ];
    drawMemHistogram(data, 'memHistogram');
  }
}
